package com.me.aop.config;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import com.me.aop.annotation.Victor;
import com.me.aop.dao.IntroductionDao;
import com.me.aop.dao.IntroductionDaoImpl;

/**
 * AOP学习: AspectJ 配置
 *      https://docs.spring.io/spring-framework/docs/4.3.12.RELEASE/spring-framework-reference/html/aop.html#aop-ataspectj
 *
 * @author ME
 * @date   2019/11/13
 */
@Component
@Aspect
public class AspectJConfig {

    /**
	 * 方法的一切定义都可以描述: 修饰符、返回类型、包名、方法名、方法参数
      */
    @Pointcut("execution(public * com.me.aop.dao.*.*(java.lang.String, ..))")
    public void pointCutExecution() {
    }

    /**
     * 指定类的方法
      */
    @Pointcut("within(com.me.aop.dao.*)")
    public void pointCutWithin() {
    }

    /**
     * 目标类方法入参对象是指定类（包含子类）时，切点匹配
      */
    @Pointcut("args(java.lang.Integer, ..)")
    public void pointCutArgs() {
    }

    /**
     * 匹配带有指定注解的方法
     */
    @Pointcut("@annotation(victor)")
    public void pointCutAnnotation(Victor victor) {
    }

    /**
     * 使用 @args： TODO 这个表达式是运行时检查，性能差，而且会创建很多额外的代理类，还是禁用吧   <p>
     * 而且这玩意为啥会切到 {@link com.me.study.factoryBean.TestFactoryBean TestFactoryBean}   <p></p>
     *
     * 该函数接收一个注解类的类名，当方法的运行时入参类型（包含子类）标注了指定的注解时   <p>
     * 在类继承树中注解点低于入参类型点，则注解点所在类及其子类作为方法入参时，该方法匹配切点 @args(M)
     */
    @Pointcut("@args(com.me.aop.annotation.Victor)")
    public void pointCutArgs2() {
    }

    /**
     * this 表示 当前对象(代理对象) 的匹配类型, this 表达式可以轻松的区分是 JDK动态代理还是 CGLIB
     *      springAOP 默认使用 JDK动态代理, 此时代理对象不是 IndexDao的实例
     *      proxyTargetClass = true, 强制使用 CGLIB, 此时代理对象是 IndexDao的实例
     */
    @Pointcut("this(com.me.aop.dao.IndexDaoImpl)")
    public void pointCutThis() {
    }

    /**
     * target 表示目标对象的匹配类型
     */
    @Pointcut("target(com.me.aop.dao.IndexDaoImpl)")
    public void pointCutTarget() {
    }



    // 匹配带有指定参数类型的方法
    // @Before("pointCutArgs()")

    // 匹配dao包下所有类中不满足 pointCutArgs()表达式，以及满足 pointCutWithin()的 任意方法
    // @Before("pointCutWithin() && !pointCutArgs()")

    // 匹配带有指定注解的方法
    @Before("pointCutExecution()")
    public void before(JoinPoint joinPoint) {
        System.out.println("【Before】 advice");
        System.out.println("【Before】 advice.getThis() : " + joinPoint.getThis());
    }

    @After("pointCutArgs2()")
    public void after() {
        System.out.println("拦截 @Victor 注解：参数类型上的");
        System.out.println("【After】 advice");
    }

    @AfterReturning("pointCutExecution()")
    public void afterReturning() {
        System.out.println("【AfterReturning】 advice");
    }

    @AfterThrowing(value = "pointCutExecution()", throwing = "ex")
    public void afterThrowing(Exception ex) {
        System.out.println("【AfterThrowing】 advice");
    }

    /** 写法一 */
    // @Around("@annotation(victor)")
    // public Object around(ProceedingJoinPoint pjp, Victor victor) throws Throwable {
    /** 写法二 */
    @Around("pointCutAnnotation(victor)")
    public Object around(ProceedingJoinPoint pjp, Victor victor) throws Throwable {
        System.out.println("========= 拦截 @Victor 注解：方法上的 =========");
        Object[] args = pjp.getArgs();
        System.out.println("【Around】 advice: before");
        Object retVal = pjp.proceed(args);

        System.out.println("【Around】 advice: after");
        return retVal;
    }

	/**
	 * 给被代理类加入新方法：
	 * 1，通过 成员变量的类型 声明需要实现的 接口；
	 * 2，使用 value 指定目标类，被代理类。   <p></p>
	 * 3，使用注解的 defaultImpl 指定接口的实现类。   <p></p>
	 *
	 * 给 TestDaoImpl（目标类，被代理类） 引入 IntroductionDao 接口, 其默认实现由 IntroductionDaoImpl.class 提供
	 */
	@DeclareParents(value = "com.me.aop.dao.TestDaoImpl", defaultImpl = IntroductionDaoImpl.class)
	public static IntroductionDao dao;

}
